<?php

namespace App\Libs\Sms;

use api\modules\manage\models\NotificationTemplate;
use api\utils\LogUtil;
use Qcloud\Sms\SmsMultiSender;
use Qcloud\Sms\SmsSingleSender;
use yii\helpers\ArrayHelper;

class TencentCloud
{
    private $appId;
    private $smsSdkAppid;
    private $smsSdkAppKey;
    private $sign = '腾讯云';
    private $version = '2019-07-11';
    private $action = 'SendSms';
    private $secretId;
    private $secretKey;
    private $host = 'sms.tencentcloudapi.com';
    private $service = 'sms';
    private $serviceProvider = 'tencentCloud';

    public function getInstance($params)
    {
        $this->appId = $params['account'];
        $this->serviceProvider = 'tencentCloud';
        !empty($params['smsSdkAppid']) && $this->smsSdkAppid = $params['smsSdkAppid'];
        !empty($params['smsSdkAppKey']) && $this->smsSdkAppKey = $params['smsSdkAppKey'];
        !empty($params['sign']) && $this->sign = $params['sign'];
        !empty($params['version']) && $this->version = $params['version'];
        !empty($params['action']) && $this->action = $params['action'];
        !empty($params['secretId']) && $this->secretId = $params['secretId'];
        !empty($params['secretKey']) && $this->secretKey = $params['secretKey'];
        !empty($params['host']) && $this->host = $params['host'];

        return $this;
    }

    /**
     * 使用 2.0 接口发送模板短信
     *
     * @see https://cloud.tencent.com/document/product/382/5976
     * @param array $phones
     * @param array $params
     * @return array ['result' => 1, 'logs' =>[]]
     */
    public function sendSms($phones, $params)
    {
        if (!empty($params['sendByApi'])) {
            return $this->sendSmsByAPi($phones, $params);
        }
        require_once(\Yii::getAlias('@extVendor') . DIRECTORY_SEPARATOR . 'qcloudsms_php-master' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'index.php');
        $template = NotificationTemplate::findOne(['id' => $params['templateId']]);
        $ext = $template->outer_config ? json_decode($template->outer_config, true) : [];
        $sendParams = [];
        foreach ($ext['configs'] as $config) {
            $value = ArrayHelper::getValue($params['object'], $config['field']);
            if ($config['type'] == 'timestamp' && is_numeric($value) && !empty($config['format'])) {
                $value = date($config['format'], $value);
            }
            $sendParams[] = $value;
        }
        try {
            if (count($phones) == 1) {
                $ssender = new SmsSingleSender($this->smsSdkAppid, $this->smsSdkAppKey);
                $result = $ssender->sendWithParam("86", $phones[0], $template->outer_id, $sendParams, $this->sign);
            } else {
                $msender = new SmsMultiSender($this->smsSdkAppid, $this->smsSdkAppKey);
                $result = $msender->sendWithParam("86", $phones, $template->outer_id, $sendParams, $this->sign);
            }
            LogUtil::warn(['message' => 'send_sms_tencent', 'phones' => $phones, 'params' => $sendParams, 'result' => json_decode($result, true)]);
            return $this->formatResponse($phones, $result, $sendParams, $template->id);
        } catch(\Exception $e) {
            throw $e;
        }
    }

    public function formatResponse($phones, $responseStr, $params, $templateId)
    {
        $logs = [];
        $response = json_decode($responseStr, true);
        $log = [
            'service_provider' => $this->serviceProvider,
            'result' => 0,
            'template_id' => $templateId,
            'request_params' => json_encode($params),
            'response' => $responseStr,
        ];
        $result = $response['result'] == 0;
        if (count($phones) == 1) {
            $log['result'] = intval($result);
            $log['phone'] = $phones[0];
            return ['result' => $result, 'logs' => [$log]];
        }
        foreach ($response['details'] ?? [] as $detail) {
            // @see https://cloud.tencent.com/document/product/382/5977
            $log['phone'] = $detail['mobile'];
            $log['result'] = intval($detail['result'] == 0);
            $logs[] = $log;
        }
        return ['result' => $result, 'logs' => $logs];
    }

    public function sendSmsByAPi($phones, $params)
    {
        $isV1 = !isset($params['version']) ? true : $params['version'] == 'v1';

        $template = NotificationTemplate::findOne(['id' => $params['templateId']]);
        $ext = $template->outer_config ? json_decode($template->outer_config, true) : [];
        $params['object']['outerId'] = $template->outer_id;
        $payload = $this->formatPayload($phones, $params['object'], $ext['configs'] ?? []);
        $timestamp = time();
        if ($isV1) {
            $queryParams = $this->convertQueryParams($payload);
            $queryParams['Nonce'] = rand();
            $queryParams['Language'] = 'zh-CN';
            $queryParams['Timestamp'] = $timestamp;
            $queryParams['Region'] = '';
            $queryParams['SecretId'] = $this->secretId;
            ksort($queryParams);
            $signature = $this->signV1($queryParams);
            $queryParams['Signature'] = $signature;
            $url = 'https://' . $this->host . '/?' . http_build_query($queryParams);
            $response = $this->_curl($url, $queryParams);
        } else {
            $sign = $this->signV3($timestamp, $payload);
            $headers = [
                'X-TC-Action' => $this->action,
                'X-TC-Timestamp' => $timestamp,
                'X-TC-Version' => $this->version,
                'Authorization' => $sign,
                'Content-Type: application/json; charset=utf-8',
            ];
            $url = 'https://' . $this->host;
            $response = $this->_curl($url, $payload, $headers);
        }

        return $this->formatResponseV3($response, $params);
    }

    private function signV1($param)
    {
        $signStr = 'GET' . $this->host . '/?';
        foreach ($param as $key => $value ) {
            $signStr = $signStr . "$key=$value&";
        }
        $signStr = substr($signStr, 0, -1);
        return base64_encode(hash_hmac('sha1', $signStr, $this->secretKey, true));
    }

    private function convertQueryParams($payload)
    {
        if (!is_array($payload)) {
            return $payload;
        }
        $qs = [];
        foreach ($payload as $key => $value) {
            if (empty($value)) {
                continue;
            }
            if (is_array($value)) {
                foreach ($value as $intKey => $v) {
                    $qs["$key.$intKey"] =$v;
                }
            } else {
                $qs[$key] = $value;
            }
        }

        return $qs;
    }

    /**
     * v1 签名 get 发送
     */
    private function _curl($url, $params, $headers = [])
    {
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_HEADER, 0);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']);
        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
        if ($headers) {
            curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
        }

        $response = curl_exec($curl);
        try {
            $response = curl_exec($curl);
        } catch (\Exception $e) {
            LogUtil::warn('Component-Request: ------ curl error ------' . $e->getMessage());
        }
        LogUtil::warn('Component-Request: -- accessout -- url: ' . $url . ' response ' . $response . ' curl_error ' . curl_error($curl));
        curl_close($curl);

        return $response;
    }

    /**
     * 格式化腾讯云发送短信接口返回
     * @see https://cloud.tencent.com/document/api/382/38778#3.-.E8.BE.93.E5.87.BA.E5.8F.82.E6.95.B0
     *
     * @return array SmsSendLog
     */
    public function formatResponseV3($response, $params = [])
    {
        if (!empty($response) && is_string($response)) {
            $response = json_decode($response, true);
        }
        $sendStatusSet = $response['Response']['SendStatusSet'] ?? [];
        $log = [
            'service_provider' => 'tencentCloud',
            'params' => json_encode($params),
        ];
        $logs = [];
        foreach ($sendStatusSet as $key => $result) {
            $recordId = $params['recordIds'][$key] ?? '';
            $log['ref_id'] = $recordId;
            $log['template_id'] = $params['outerId'];
            $log['request_id'] = $response['Response']['RequestId'];
            $log['serial_no'] = $result['SerialNo'];
            $log['phone'] = $result['PhoneNumber'];
            $log['response'] = json_encode($result);
            $log['code'] = $result['Code'];
            $log['message'] = $result['Message'];
            array_push($logs, $log);
        }

        return [
            'items' => $logs,
            'requestId' => $response['Response']['RequestId'],
        ];
    }

    private function formatPayload($phones, $params, $configs)
    {
        $payload = [
            'Action' => $this->action,
            'Version' => $this->version,
            'TemplateID' => $params['outerId'],
            'SmsSdkAppid' => $this->smsSdkAppid,
            'Sign' => $this->sign,
        ];
        $paramSet = [];
        foreach ($configs as $config) {
            $paramSet[] = $params[$config['field']];
        }
        $phoneSet = [];
        foreach ($phones as $phone) {
            $phoneSet[] = "+86$phone";
        }
        $payload['TemplateParamSet'] = $paramSet;
        $payload['PhoneNumberSet'] = $phoneSet;

        return $payload;
    }

    private function signV3($timestamp, $payload)
    {
        $secretId = $this->secretId;
        $secretKey = $this->secretKey;
        $host = $this->host;
        $service = $this->service;
        $algorithm = 'TC3-HMAC-SHA256';

        // step 1: build canonical request string
        $httpRequestMethod = 'POST';
        $canonicalUri = '/';
        $canonicalQueryString = '';
        $canonicalHeaders = "content-type:application/json; charset=utf-8\nhost:$host\n";
        $signedHeaders = 'content-type;host';
        if (is_array($payload)) {
            $payload = json_encode($payload);
        }
        $hashedRequestPayload = hash('SHA256', $payload);
        $canonicalRequest = $httpRequestMethod . "\n"
            . $canonicalUri . "\n"
            . $canonicalQueryString . "\n"
            . $canonicalHeaders . "\n"
            . $signedHeaders . "\n"
            . $hashedRequestPayload;
        // echo $canonicalRequest.PHP_EOL;

        // step 2: build string to sign
        $date = gmdate('Y-m-d', $timestamp);
        $credentialScope = "$date/$service/tc3_request";
        $hashedCanonicalRequest = hash('SHA256', $canonicalRequest);
        $stringToSign = "$algorithm\n$timestamp\n$credentialScope\n$hashedCanonicalRequest";
        // echo $stringToSign.PHP_EOL

        // step 3: sign string
        $secretDate = hash_hmac("SHA256", $date, "TC3" . $secretKey, true);
        $secretService = hash_hmac("SHA256", $service, $secretDate, true);
        $secretSigning = hash_hmac("SHA256", "tc3_request", $secretService, true);
        $signature = hash_hmac("SHA256", $stringToSign, $secretSigning);
        // echo $signature.PHP_EOL;

        // step 4: build authorization
        $authorization = $algorithm
            . " Credential=" . $secretId . "/" . $credentialScope
            . ", SignedHeaders=content-type;host, Signature=" . $signature;

        return $authorization;
    }
}
